package com.zjp.mg.dao;

import com.mongodb.client.result.UpdateResult;
import com.zjp.mg.core.*;
import org.bson.json.JsonMode;
import org.bson.json.JsonWriterSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.BulkOperations;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.mapping.MongoId;
import org.springframework.data.mongodb.core.query.*;
import org.springframework.data.repository.support.PageableExecutionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

@SuppressWarnings("unchecked")
public abstract class AbstractMongoService<T> extends BaseService implements MgDao<T> {


    private final Logger logger = LoggerFactory.getLogger(this.getClass());


    /**
     * getMongoTemplate()是线程安全的，可以在多个实例中重用
     */
    /*@Resource
    public getMongoTemplate() getMongoTemplate();*/

    @Autowired
    DynamicMongoDBDataBase dynamicMongoDBDataBase;

    public MongoTemplate getMongoTemplate() {
        return dynamicMongoDBDataBase.getMongo();
    }

    /**
     * @return
     */
    public abstract String getCollectionName();


    /**
     * @return
     */
    public abstract Class<T> getType();

    private Class<T> getClassType() {
        Type type = getClass().getGenericSuperclass();
        Class<T> result = null;
        if (type instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) type;
            result = (Class<T>) pType.getActualTypeArguments()[0];
        }
        return result;
    }


    @Override
    public GridModel<T> queryPage(Integer page, Integer size, Class<T> entityClass, Query query) {
        if (page == 0) {
            page = 1;
        }
        if (size == 0) {
            size = 10;
        }
        //查询总数
        int count = (int) getMongoTemplate().count(query, getCollectionName());
        GridModel<T> gridModel = new GridModel<T>();
        gridModel.setPage(page);
        gridModel.setRecords((long) count);
        if (count > 0) {
            gridModel.setTotal(count / size + 1);
        }

        //排序
        query.skip((page - 1) * size).limit(size);
        List<T> dataList = getMongoTemplate().find(query, getType(), getCollectionName());
        gridModel.setRows(dataList);
        return gridModel;
    }


    public Long getNextAutoId() {
        Query query = new Query(Criteria.where(COLL_NAME).is(getCollectionName()));
        Update update = new Update();
        update.inc(SEQ_Id, INCREMENT_VALUE);
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.upsert(true);
        options.returnNew(true);
        SeqInfo seq = getMongoTemplate()
                .findAndModify(query, update, options, SeqInfo.class);
        return seq.getSeqId();
    }

    public T insert(T object) {
        return getMongoTemplate().insert(object, getCollectionName());
    }

    @Override
    public void save(T object) {
        getMongoTemplate().save(object, getCollectionName());
    }

    @Override
    public void insert(Collection<?> batchToSave) {
        getMongoTemplate().insert(batchToSave, getCollectionName());
    }

    @Override
    public long remove(Query query) {
        return getMongoTemplate().remove(query, getCollectionName()).getDeletedCount();
    }

    @Override
    public long removeByObject(T object) {
        return getMongoTemplate().remove(object).getDeletedCount();
    }

    @Override
    public List<T> find(Query query) {
        return getMongoTemplate().find(query, getType(), getCollectionName());
    }

    public <D> List<D> findDistinct(String field, Class<D> resultClass) {
        return getMongoTemplate().findDistinct(field, getType(), resultClass);
    }

    public <D> List<D> findDistinct(Query query, String field, Class<D> resultClass) {
        return getMongoTemplate().findDistinct(query, field, getType(), resultClass);
    }

    @Override
    public List<T> findAll() {
        return getMongoTemplate().findAll(getType(), getCollectionName());
    }

    @Override
    public long count(Query query) {
        return getMongoTemplate().count(query, getCollectionName());
    }

    @Override
    public T findOne(Query query) {
        return getMongoTemplate().findOne(query, getType(), getCollectionName());
    }

    @Override
    public T findById(Object id) {
        return getMongoTemplate().findById(id, getType(), getCollectionName());
    }

    @Override
    public UpdateResult update(Query query, UpdateDefinition updateDefinition) {
        return getMongoTemplate().updateMulti(query, updateDefinition, getCollectionName());
    }

    // 批量操作 （适用于对象数组多个值更新的场景）
    public BulkOperations bulkOps(BulkOperations.BulkMode bulkMode) {
        return getMongoTemplate().bulkOps(bulkMode, getCollectionName());
    }

    public UpdateResult updateFirst(Query query, UpdateDefinition update) {
        return getMongoTemplate().updateFirst(query, update, getCollectionName());
    }

    public UpdateResult updateFirst(T t) {
        UpdateCondition updateCondition = initUpdateCondition(t);
        return updateFirst(updateCondition.getQuery(), updateCondition.getUpdate());
    }

    public UpdateCondition initUpdateCondition(T t) {
        Class<T> classType = getType();
        Field[] fields = classType.getDeclaredFields();
        Query query = new Query();
        Update update = new Update();
        for (Field field : fields) {
            int modifiers = field.getModifiers();
            if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
                continue;
            }
            // 强制访问
            field.setAccessible(true);
            MongoId mongoId = field.getAnnotation(MongoId.class);
            Id id = field.getAnnotation(Id.class);
            if (Objects.nonNull(mongoId) || Objects.nonNull(id)) {
                try {
                    query.addCriteria(Criteria.where("_id").is(field.get(t)));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            } else {
                try {
                    Object o = field.get(t);
                    if (Objects.nonNull(o)) {

                        org.springframework.data.mongodb.core.mapping.Field
                                annotation = field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);

                        if (Objects.nonNull(annotation)) {
                            update.set(annotation.value(), o);
                        } else {
                            update.set(field.getName(), o);
                        }
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

        if (logger.isInfoEnabled()) {
            logger.info("--query--{}", query.getQueryObject().toJson(JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()));
            logger.info("--update--{}", update.getUpdateObject().toJson(JsonWriterSettings.builder().outputMode(JsonMode.SHELL).build()));
        }

        return new UpdateCondition(query, update);
    }


    public Page<T> page(Query with, Pageable pageable, Long count) {
        with.with(pageable);
        List<T> schemeInfos = find(with);

        return PageableExecutionUtils.getPage(schemeInfos, pageable, () -> count);
    }

    public Page<T> page(Query with, Pageable pageable) {
        long count = count(with);
        return page(with, pageable, count);
    }


    public IndexOperations indexOps() {
        return getMongoTemplate().indexOps(getCollectionName());
    }


    public GeoResults<T> geoNear(NearQuery nearQuery) {
        return getMongoTemplate().geoNear(nearQuery, getType(), getCollectionName());
    }


}
